#ifndef DISTRIBUTEMESH_HPP
#define DISTRIBUTEMESH_HPP

#include "meshTypes_fwd.hpp"
#include "meshTypes.hpp"
#include "mpiHelpers.hpp"
#include "structMeshPart.hpp"
#include "structMeshFunctions.hpp"

namespace hms {

/**
 * @brief Returns the index of the first edge at the specified boundary
 * location, according to the provided mesh size.
 * 
 * @param loc The boundary location (bottom, left, right or top)
 * @param meshSize The mesh size in x- and y-dimension.
 * @return Index 
 * 
 * @see lastEdge()
 */
Index firstEdge( BoundaryLocation loc, const Ref<const Array2i>& meshSize );
/**
 * @brief Returns the index of the last edge at the specified boundary location,
 * according to the provided mesh size.
 * 
 * @see firstEdge()
 */
Index lastEdge( BoundaryLocation loc, const Ref<const Array2i>& meshSize );

/**
 * @brief Determines the global boundary patch limits and distributes them to
 * all ranks.
 * 
 * @tparam MeshType 
 * @param part 
 * @return Array2Xi Each patch fills one column, with the first row being the
 * minimum index and the second row the maximum index.
 */
template<typename MeshType>
Array2Xi allgatherBoundaryPatchLimits( const StructMeshPart<MeshType>& part );


/**
 * @brief Distributes the boundary patches from master to all ranks.
 * 
 * @tparam MeshType 
 * @param part 
 * @param mesh A mutable reference is required.
 * 
 * When the global mesh is assigned on master and distributed, the boundary
 * patches must be distributed as well, and each rank must determine whether
 * its mesh lies at each patch.
 */
template<typename MeshType>
void distributeBoundaryPatches(
	StructMeshPart<MeshType>& part,
	MeshType& mesh
);

/**
 * @brief Redistributes boundary patches, to account for mesh partitions moving
 * around on redistribution.
 * 
 * @tparam MeshType 
 * @param part 
 * @param mesh 
 */
template<typename MeshType>
void redistributeBoundaryPatches(
	StructMeshPart<MeshType>& part,
	MeshType& mesh
);

/**
 * @brief Distributes a global UniMesh from master to all ranks.
 * 
 * @param comm The communicator.
 * @param meshSizeGlobal The global mesh size in both dimensions.
 * @param meshSizeLocal The local mesh size in both dimensions.
 * @param offsetLocal The position of the local mesh within the global mesh.
 * @return UniMesh The local mesh.
 * 
 * Only distributes data pertaining to the mesh type, does not distribute 
 * boundary patches or ghost cell layer count.
 */
UniMesh distributeMesh(
	const UniMesh&,
	const MPI_Comm_H& comm,
	const Ref<const Array2i>& meshSizeGlobal,
	const Ref<const Array2i>& meshSizeLocal,
	const Ref<const Array2i>& offsetLocal
);

/**
 * @brief Distributes a global RectMesh from master to all ranks.
 * @overload
 * @see distributeMesh()
 */
RectMesh distributeMesh(
	const RectMesh& mesh,
	const MPI_Comm_H& comm,
	const Ref<const Array2i>& meshSizeGlobal,
	const Ref<const Array2i>& meshSizeLocal,
	const Ref<const Array2i>& offset
);

/**
 * @brief Distributes a global StructMesh from master to all ranks.
 * @overload
 * @see distributeMesh()
 */
StructMesh distributeMesh(
	const StructMesh& mesh,
	const MPI_Comm_H& comm,
	const Ref<const Array2i>& meshSizeGlobal,
	const Ref<const Array2i>& meshSize,
	const Ref<const Array2i>& offset
);


/**
 * @brief Redistributes the data pertaining to the mesh type and updates the
 * passed-in mesh accordingly.
 * 
 * @param comm The communicator.
 * @param part The partition's index within its band and the band.
 * @param nParts The number of partitions in x-direction (number per band) and
 * y-direction (number of bands).
 * @param meshSize The new local mesh size.
 * @param offset The new local mesh offset.
 * @param allSizes All old mesh sizes.
 * @param allOffsets All old mesh offsets.
 * 
 * Only data pertaining to the mesh type is redistributed. Especially boundary
 * patches must be redistributed separately.
 */
void redistributeMesh(
	UniMesh&,
	const MPI_Comm_H& comm,
	const Ref<const Array2i>& part,
	const Ref<const Array2i>& nParts,
	const Ref<const Array2i>& meshSize,
	const Ref<const Array2i>& offset,
	const Ref<const Array2Xi>& allSizes,
	const Ref<const Array2Xi>& allOffsets
);

/**
 * @overload
 * @see redistributeMesh()
 * 
 * Due to the coordinate data involved, this is a more expensive operation than
 * redistributing a UniMesh.
 */
void redistributeMesh(
	RectMesh& mesh,
	const MPI_Comm_H& comm,
	const Ref<const Array2i>& part,
	const Ref<const Array2i>& nParts,
	const Ref<const Array2i>& meshSize,
	const Ref<const Array2i>& offset,
	const Ref<const Array2Xi>& allSizes,
	const Ref<const Array2Xi>& allOffsets
);

/**
 * @overload
 * @see redistributeMesh()
 * 
 * Due to the amount of data involved, this is a much more expensive operation
 * than redistributing a UniMesh.
 */
void redistributeMesh(
	StructMesh& mesh,
	const MPI_Comm_H& comm,
	const Ref<const Array2i>& part,
	const Ref<const Array2i>& nParts,
	const Ref<const Array2i>& meshSize,
	const Ref<const Array2i>& offset,
	const Ref<const Array2Xi>& allSizes,
	const Ref<const Array2Xi>& allOffsets
);

}

#include "structMeshDistribution.tpp"

#endif